import * as Y from 'yjs';
import * as syncProtocol from 'y-protocols/sync.js';
import * as awarenessProtocol from 'y-protocols/awareness.js';
// lib0 是 Yjs 生态提供的高性能工具函数库，目的是在浏览器和 Node 中实现更高效、更精简的基础功能，
// 服务于协同编辑这种对性能要求极高的场景。
import * as encoding from 'lib0/encoding';
import * as decoding from 'lib0/decoding';
import * as map from 'lib0/map';

import { IPersistence, IWSSharedDoc } from './interfaces.js';

// WebSocket 连接状态常量
const wsReadyStateConnecting = 0;
const wsReadyStateOpen = 1;
const wsReadyStateClosing = 2; // eslint-disable-line
const wsReadyStateClosed = 3; // eslint-disable-line

// 根据环境变量决定是否启用垃圾回收（GC），用于调试或快照兼容性
const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0';

let persistence: IPersistence | null = null;

// 设置持久化模块
export const setPersistence = (persistence_: IPersistence) => {
  persistence = persistence_;
};

// 获取当前的持久化模块
export const getPersistence = () => persistence;

// 内存中的所有 Y.Doc 实例集合（通过文档名索引）
export const docs = new Map<string, IWSSharedDoc>();

const messageSync = 0; // 同步消息类型
const messageAwareness = 1; // 用户状态更新类型
// const messageAuth = 2;    // 可选：认证类型

// 文档更新时的广播处理器
const updateHandler = (update: Uint8Array, origin: any, doc: Y.Doc) => {
  const sharedDoc = doc as IWSSharedDoc;

  const encoder = encoding.createEncoder();
  encoding.writeVarUint(encoder, messageSync);
  syncProtocol.writeUpdate(encoder, update);
  const message = encoding.toUint8Array(encoder);

  // 将更新广播给所有连接的客户端
  sharedDoc.conns.forEach((_, conn) => send(sharedDoc, conn, message));
};

// 带有 awareness 和连接管理功能的 Y.Doc 扩展
class WSSharedDoc extends Y.Doc implements IWSSharedDoc {
  // 文档名称，用于标识唯一文档
  name: string;
  // 存储连接对象与其控制的 clientIDs 的映射（每个连接可能控制多个客户端 ID）
  conns: Map<Object, Set<number>>;
  // awareness 实例，用于处理用户状态（例如光标位置、用户名等）
  awareness: awarenessProtocol.Awareness;

  constructor(name: string) {
    // 创建一个 Y.Doc 实例，同时可传入是否开启垃圾回收（gc）选项
    super({ gc: gcEnabled });
    this.name = name;
    this.conns = new Map(); // 初始化连接映射表
    this.awareness = new awarenessProtocol.Awareness(this); // 初始化 awareness 实例
    this.awareness.setLocalState(null);

    // 设置 awareness 更新处理器（当客户端更新状态时触发）
    const awarenessChangeHandler = (
      {
        added,
        updated,
        removed,
      }: {
        added: Array<number>; // 新增的客户端 ID
        updated: Array<number>; // 更新状态的客户端 ID
        removed: Array<number>; // 离开的客户端 ID
      },
      conn: Object | null, // 触发更新的连接对象
    ) => {
      // 收集所有变更的客户端 ID
      const changedClients = added.concat(updated, removed);
      // 如果是某个特定连接发起的状态更新
      if (conn !== null) {
        const connControlledIDs = this.conns.get(conn); // 获取该连接管理的 client IDs
        if (connControlledIDs !== undefined) {
          // 将新增的 ID 加入集合
          added.forEach((clientID) => connControlledIDs.add(clientID));
          // 将移除的 ID 从集合中删除
          removed.forEach((clientID) => connControlledIDs.delete(clientID));
        }
      }
      // 广播 awareness 状态更新
      const encoder = encoding.createEncoder();
      encoding.writeVarUint(encoder, messageAwareness); // 写入消息类型（awareness 更新）
      encoding.writeVarUint8Array(
        encoder,
        awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients),
      ); // 编码状态变更
      const buff = encoding.toUint8Array(encoder); // 转为二进制数组
      // 向所有连接广播这个状态更新
      this.conns.forEach((_, c) => send(this, c, buff));
    };

    // 注册事件监听器：当 awareness 状态变更时触发
    this.awareness.on('update', awarenessChangeHandler);
    // 注册 Y.Doc 的更新事件监听器（例如文档内容变更）
    this.on('update', updateHandler);
  }
}

// 获取或创建 Y.Doc
export const getYDoc = (docname: string, gc = true) =>
  map.setIfUndefined(docs, docname, () => {
    const doc = new WSSharedDoc(docname);
    doc.gc = gc;
    if (persistence !== null) {
      persistence.bindState(docname, doc);
    }
    docs.set(docname, doc);
    return doc;
  });

/**
 * 处理来自客户端的 WebSocket 消息（文档同步、用户状态更新）
 * @param conn 当前 WebSocket 连接
 * @param doc 当前协同文档对象
 * @param message 客户端发来的消息（二进制格式）
 */
const messageListener = (conn: any, doc: IWSSharedDoc, message: Uint8Array) => {
  try {
    // 创建编码器和解码器
    const encoder = encoding.createEncoder();
    const decoder = decoding.createDecoder(message);
    // 读取消息类型
    const messageType = decoding.readVarUint(decoder);
    switch (messageType) {
      // 文档同步相关消息
      case messageSync:
        encoding.writeVarUint(encoder, messageSync);
        syncProtocol.readSyncMessage(decoder, encoder, doc, conn);
        if (encoding.length(encoder) > 1) {
          send(doc, conn, encoding.toUint8Array(encoder));
        }
        break;
      // 用户状态（awareness）更新消息
      case messageAwareness: {
        awarenessProtocol.applyAwarenessUpdate(
          doc.awareness,
          decoding.readVarUint8Array(decoder),
          conn,
        );
        break;
      }
    }
  } catch (err) {
    console.error(err);
  }
};

// 关闭连接并清理资源
const closeConn = (doc: IWSSharedDoc, conn: any) => {
  if (doc.conns.has(conn)) {
    const controlledIds = doc.conns.get(conn);
    doc.conns.delete(conn);
    if (controlledIds) {
      awarenessProtocol.removeAwarenessStates(
        doc.awareness,
        Array.from(controlledIds),
        null,
      );
    }
    if (doc.conns.size === 0 && persistence !== null) {
      // 若无活跃连接且有持久化，保存状态并销毁文档
      persistence.writeState(doc.name, doc).then(() => doc.destroy());
      docs.delete(doc.name);
    }
  }
  conn.close();
};

// 安全地发送消息给某个连接
const send = (doc: IWSSharedDoc, conn: any, m: Uint8Array) => {
  if (
    conn.readyState !== wsReadyStateConnecting &&
    conn.readyState !== wsReadyStateOpen
  ) {
    closeConn(doc, conn);
  }
  try {
    conn.send(m, (err: any) => {
      err != null && closeConn(doc, conn);
    });
  } catch (e) {
    closeConn(doc, conn);
  }
};

const pingTimeout = 30000; // 每 30 秒发送 ping 包

// 设置 WebSocket 连接监听和初始化逻辑
export const setupWSConnection = (
  conn: any, // 表示客户端的 WebSocket 连接对象
  req: any, // 当前 HTTP 请求对象（可以用于获取 url 等信息）
  // 可选参数：文档名称和是否启用垃圾回收
  { docName = req.url.slice(1).split('?')[0], gc = true } = {},
) => {
  // 1.表明此连接使用二进制数据进行通信
  conn.binaryType = 'arraybuffer';
  // 2.获取或创建对应的 Y.Doc 实例
  const doc = getYDoc(docName, gc);
  // 3.将当前连接加入 doc.conns 映射表
  doc.conns.set(conn, new Set());

  // 4.设置消息监听器
  // 每当前端发送消息，会调用 messageListener，用于解析同步消息、更新 Yjs 文档
  conn.on('message', (message: ArrayBuffer) =>
    messageListener(conn, doc, new Uint8Array(message)),
  );

  // 5.启动心跳检测机制（ping/pong），保持连接活跃
  // 每隔 pingTimeout 毫秒检查一次连接是否活跃。
  // 如果超过 pingTimeout 毫秒未收到 pong，则关闭连接。
  let pongReceived = true;
  const pingInterval = setInterval(() => {
    if (!pongReceived) {
      if (doc.conns.has(conn)) {
        closeConn(doc, conn);
      }
      clearInterval(pingInterval);
    } else if (doc.conns.has(conn)) {
      pongReceived = false;
      try {
        conn.ping(); // 向客户端发送 ping
      } catch (e) {
        closeConn(doc, conn);
        clearInterval(pingInterval);
      }
    }
  }, pingTimeout);

  // 6.监听连接关闭事件
  conn.on('close', () => {
    closeConn(doc, conn);
    clearInterval(pingInterval);
  });
  // 7.监听客户端 pong 响应
  conn.on('pong', () => {
    pongReceived = true;
  });

  // 8.初始化同步：发送文档和状态
  {
    // 第一步：发送 Yjs 文档快照
    const encoder = encoding.createEncoder();
    encoding.writeVarUint(encoder, messageSync);
    syncProtocol.writeSyncStep1(encoder, doc);
    send(doc, conn, encoding.toUint8Array(encoder));

    // 第二步：发送 awareness 状态（用户光标、用户名等）
    const awarenessStates = doc.awareness.getStates();
    if (awarenessStates.size > 0) {
      const encoder = encoding.createEncoder();
      encoding.writeVarUint(encoder, messageAwareness);
      encoding.writeVarUint8Array(
        encoder,
        awarenessProtocol.encodeAwarenessUpdate(
          doc.awareness,
          Array.from(awarenessStates.keys()),
        ),
      );
      send(doc, conn, encoding.toUint8Array(encoder));
    }
  }
};
